package com.s.core.persist;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.s.core.daily_import.vo.ImportResult;
import com.s.core.root.*;
import com.s.core.persist.mapper.BasicMapper;
import com.s.core.persist.mapper.DailyMapper;
import com.s.core.root.data._AllData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Stream;

@Service
@Slf4j
public class PersistService {
    public static final boolean DEV_FOR_USE_DB = false;
    public static String DEV_FOR_USE_DB_LAST_DATE = null;

    static {
        if (!DEV_FOR_USE_DB && DEV_FOR_USE_DB_LAST_DATE != null) {
            throw new RuntimeException();
        }
    }

    private static final String BASE_PATH = (com.s.S.IS_JAR ? System.getProperty("user.dir") : "D:\\software\\mydr") + File.separator + "store";
    private static final String CORE_DATA_PATH = BASE_PATH + File.separator + "ser" + File.separator + "core";
    private static final String CORE_DATA_NAME = "%s.bin";
    private static final String S_DATA_PATH = BASE_PATH + File.separator + "ser";
    private static final String S_DATA_NAME = "s";
    private static final String CASE_DATA_PATH = BASE_PATH + File.separator + "cases.txt";
    private static final String DUMP_PATH = BASE_PATH + File.separator + "db_dump" + File.separator + "%s.db";

    private static class MYSQL_DUMP {
        private static final String PATH = "C:\\Program Files\\MySQL\\MySQL Server 5.7\\bin\\mysqldump";
        private static final String USER = "root";
        private static final String PASS = "123456";
        private static final String DB = "stock_final_3";
    }

    @Resource
    private BasicMapper basicMapper;
    @Resource
    private DailyMapper dailyMapper;

    public CoreData load() {
        return DEV_FOR_USE_DB ? loadDb() : loadCore();
    }

    private Kryo coreKryo() {
        Kryo kryo = new Kryo();
        kryo.register(Basic.class);
        kryo.register(D.class);
        kryo.register(CoreData.class);
        kryo.register(ArrayList.class);
        return kryo;
    }

    private Kryo sKryo() {
        Kryo kryo = new Kryo();
        kryo.register(S.class);
        kryo.register(SData.class);
        kryo.register(ArrayList.class);
        return kryo;
    }

    private CoreData loadDb() {
        List<Basic> basicList = basicMapper.findBasicList();
        String recentDate = DEV_FOR_USE_DB_LAST_DATE == null ? "99999999" : DEV_FOR_USE_DB_LAST_DATE;
        List<D> dList = dailyMapper.allDaily(recentDate, _AllData.LOAD_DAYS);
        return new CoreData(basicList, dList);
    }

    private CoreData loadCore() {
        File dir = new File(CORE_DATA_PATH);
        if (!dir.exists() || !dir.isDirectory() || Objects.requireNonNull(dir.list()).length == 0) {
            throw new RuntimeException();
        }
        String fileName = Arrays.stream(Objects.requireNonNull(dir.list())).max(String::compareTo).orElse(null);
        String filePath = dir.getPath() + File.separator + fileName;
        Kryo kryo = coreKryo();
        try (Input input = new Input(Files.newInputStream(Paths.get(filePath)))) {
            return (CoreData) kryo.readClassAndObject(input);
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }

    public void saveCore(String date) {
        File dir = new File(CORE_DATA_PATH);
        if (!dir.exists() || !dir.isDirectory()) {
            throw new RuntimeException();
        }
        String filePath = dir.getPath() + File.separator + String.format(CORE_DATA_NAME, date);
        if (new File(filePath).exists()) {
            throw new RuntimeException();
        }
        Kryo kryo = coreKryo();
        try (Output output = new Output(Files.newOutputStream(Paths.get(filePath)))) {
            kryo.writeClassAndObject(output, new CoreData(Ad.me().getBasicList(), Ad.me().getDList()));
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }

    public SData loadS() {
        File dir = new File(S_DATA_PATH);
        if (!dir.exists() || !dir.isDirectory()) {
            throw new RuntimeException();
        }
        String filePath = dir.getPath() + File.separator + S_DATA_NAME;
        if (!new File(filePath).exists()) {
            throw new RuntimeException();
        }
        Kryo kryo = sKryo();
        try (Input input = new Input(Files.newInputStream(Paths.get(filePath)))) {
            return (SData) kryo.readClassAndObject(input);
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }

    public void saveS() {
        File dir = new File(S_DATA_PATH);
        if (!dir.exists() || !dir.isDirectory()) {
            throw new RuntimeException();
        }
        String filePath = dir.getPath() + File.separator + S_DATA_NAME;
        Kryo kryo = sKryo();
        try (Output output = new Output(Files.newOutputStream(Paths.get(filePath)))) {
            kryo.writeClassAndObject(output, new SData(As.me().getSList()));
        } catch (Exception e) {
            throw new RuntimeException();
        }
    }

    public void saveDb(ImportResult result) {
        result.adds.forEach(add -> {
            if (basicMapper.add(add.code, add.name) != 1) {
                throw new RuntimeException();
            }
        });
        result.removeCodes.forEach(code -> {
            if (basicMapper.del(code) <= 0) {
                throw new RuntimeException();
            }
            if (dailyMapper.del(code) <= 0) {
                throw new RuntimeException();
            }
        });
        result.renames.forEach(rename -> {
            if (basicMapper.rename(rename.code, rename.newName) <= 0) {
                throw new RuntimeException();
            }
        });
        result.addD.forEach(d -> dailyMapper.add(d.code, d.close, d.vol, d.getPos1(), d.date, d.preClose, d.high, d.pos2, d.pos3, d.pos4, d.pos5, d.pos6, d.pos7, d.pos8, d.pos9, d.pos10, d.pos11, d.pos12, d.pos13, d.pos14, d.pos15, d.pos16, d.pos17, d.pos18, d.pos19, d.pos20, d.pos21, d.pos22, d.pos23, d.pos24, d.pos25, d.pos30, d.pos40, d.pos50, d.pos60, d.pos80, d.pos100, d.pos120));
    }

    /**
     * 备份：mysqldump -u root -p stock_final_3 > D:\\code\\20250127.db
     * 还原：mysql -u root -p  stock_final_3 < D:\\code\\20250127.db
     */
    public void dump(String date) {
        String filePath = String.format(DUMP_PATH, date);
        if (new File(filePath).exists()) {
            log.info("dump {}.db exist", date);
            return;
        }
        log.info("start dump {}.db", date);
        File outputFile = new File(filePath);
        ProcessBuilder processBuilder = new ProcessBuilder(MYSQL_DUMP.PATH, "-u", MYSQL_DUMP.USER, "--password=" + MYSQL_DUMP.PASS, MYSQL_DUMP.DB);
        processBuilder.redirectOutput(outputFile);
        processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
        try {
            Process process = processBuilder.start();
            int exitCode = process.waitFor();
            if (exitCode == 0) {
                log.info("success dump {}.db", date);
                return;
            }
            log.error("dump err,exitCode: {}", exitCode);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    public List<CaseA> loadCases() {
        List<CaseA> caseAList = new ArrayList<>();
        try (Stream<String> stream = Files.lines(Paths.get(CASE_DATA_PATH))) {
            stream.forEach(l -> {
                CaseA caseA = CaseA.line2CaseA(l);
                if (caseA != null) {
                    caseAList.add(caseA);
                }
            });
        } catch (Exception e) {
            throw new RuntimeException();
        }
        return caseAList;
    }
}
